/**
 * 
 */
package com.couchcrowler.client.ui;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.couchcrowler.client.Couchcrowler;
import com.couchcrowler.client.event.ErrorHandler;
import com.couchcrowler.client.event.LoadingStatusHandler;
import com.couchcrowler.client.event.ProfileLoadedHandler;
import com.couchcrowler.client.service.ProfileLoader;
import com.couchcrowler.shared.Location;
import com.couchcrowler.shared.Profile;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.maps.client.InfoWindowContent;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.control.LargeMapControl;
import com.google.gwt.maps.client.event.MarkerClickHandler;
import com.google.gwt.maps.client.event.PolylineClickHandler;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.overlay.Marker;
import com.google.gwt.maps.client.overlay.Overlay;
import com.google.gwt.maps.client.overlay.Polyline;
import com.google.gwt.maps.client.overlay.PolylineOptions;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

/**
 * @author Vincentas
 *
 */
public class MapPanel extends Composite implements ProfileLoadedHandler, LoadingStatusHandler, ErrorHandler {

	private static final ProfileLoader profileLoader = new ProfileLoader();
	
	private static MapPanelUiBinder uiBinder = GWT.create(MapPanelUiBinder.class);

	interface MapPanelUiBinder extends UiBinder<Widget, MapPanel> {
	}
	
	@UiField
	MapWidget mapWidget;
	
	@UiField
	TextBox idTextBox;
	
	@UiField
	Button searchButton;
	
	@UiField
	Label label;
	
	@UiField
	DialogBox dialog;
	
	@UiField
	TextArea log;
	
	private String currentProfileId;
	
	private Profile currentProfile;
	
	public MapPanel() {
		initWidget(uiBinder.createAndBindUi(this));
		
		mapWidget.setZoomLevel(5);
		mapWidget.addControl(new LargeMapControl());
		
		Couchcrowler.MANAGER.addHandler(ProfileLoadedHandler.type, this);
		Couchcrowler.MANAGER.addHandler(LoadingStatusHandler.type, this);
		Couchcrowler.MANAGER.addHandler(ErrorHandler.type, this);
	}
	
	
	private boolean isCurrentProfile(Profile profile) {
		return profile.getId().equals(currentProfileId);
	}
	
	private void setCurrentProfile(String id) {
		currentProfileId = id;
		profileLoader.loadProfile(id);
	}
	
	@UiHandler("searchButton")
	public void goSearch(ClickEvent clickEvent) {
		setCurrentProfile(idTextBox.getText());
	}
	
	private Profile polylineProfile;
	
	private void addMarker(final Profile profile) {
		final Marker marker = new Marker(convert(profile.getLocation()));		
		marker.addMarkerClickHandler(new MarkerClickHandler() {
			
			@Override
			public void onClick(MarkerClickEvent event) {
				showInfoWindow(profile);
			}
		});
		
		addToMap(marker);
	}
	
	private void handleProfile(final Profile profile) {
		addMarker(profile);
		addPolyline(profile);
	}
	
	private List<Profile> missingPolylines = new ArrayList<Profile>();
	
	private void addPolyline(final Profile profile) {
		if (currentProfile == null) {
			missingPolylines.add(profile);
		} else if (!isCurrentProfile(profile)) {
			Polyline polyline = new Polyline(new LatLng[] {convert(currentProfile.getLocation()), convert(profile.getLocation())}, "#000070", 3, 0.8, PolylineOptions.newInstance(true, true));
			polyline.addPolylineClickHandler(new PolylineClickHandler() {
				
				@Override
				public void onClick(PolylineClickEvent event) {
					if (profile == polylineProfile) {
						polylineProfile = currentProfile;
					} else {
						polylineProfile = profile;
					}
					
					mapWidget.panTo(convert(polylineProfile.getLocation()));
					showInfoWindow(polylineProfile);
				}
			});
			
			addToMap(polyline);
		} else {
			for (Profile missingProfile : missingPolylines) {
				addPolyline(missingProfile);
			}
			
			missingPolylines.clear();
		}
	}
	
	private Set<Overlay> overlays = new HashSet<Overlay>();
	
	private Set<Profile> pinned = new HashSet<Profile>();
	
	private void addToMap(Overlay overlay) {
		overlays.add(overlay);
		mapWidget.addOverlay(overlay);
	}
	
	private LatLng convert(Location location) {
		return LatLng.newInstance(location.getLatitude(), location.getLongitude());
	}
	
	private void showInfoWindow(final Profile profile) {
		Image.prefetch(profile.getPictureUrl());
		
		final Image image = new Image(profile.getPictureUrl());
		InfoWindowContent content = new InfoWindowContent(image);
		mapWidget.getInfoWindow().open(convert(profile.getLocation()), content);

		image.addClickHandler(new ClickHandler() {
			
			@Override
			public void onClick(ClickEvent event) {
				mapWidget.getInfoWindow().close();
				for (Overlay overlay : overlays) {
					mapWidget.removeOverlay(overlay);
				}
				
				overlays.clear();
				
				for (Profile pinnedProfile : pinned) {
					addMarker(pinnedProfile);
				}
				
				setCurrentProfile(profile.getId());
			}
		});
	}

	@Override
	public void loaded(Profile profile) {
		if (isCurrentProfile(profile)) {	
			currentProfile = profile;
			
			mapWidget.panTo(convert(profile.getLocation()));
			pinned.add(profile);
			
			profileLoader.loadProfiles(profile.getContactsIds());
		}
		
		handleProfile(profile);		
	}


	@Override
	public void loadingStatusUpdate(int itemsLoaded, int itemsLeft,	@SuppressWarnings("rawtypes") Class loaderClass) {
		if (itemsLeft == 0) {
			label.setText("All profiles loaded.");
		} else {
			label.setText("Loading profiles... (" + itemsLeft + ")");
		}
	}

	@Override
	public void handleError(Throwable throwable) {
		throwable.printStackTrace();
		log.setText(throwable.toString());
		dialog.showRelativeTo(mapWidget);
	}
}
